<?php
namespace DRY\Document;

/**
 * The "Record" model stores a document as a row in a table in a database.
 * Provides functionality to insert, select, update and delete that row ( CRUD )
 * Supports direct searching of attributes inside marked fields (for very fast document discovery), however it can also search in the serialized attributes
 *   (however much slower and with high memory usage, because the entire "documents" table must be selected and attributes searched for)
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/Modules/document.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage Module.Document
 */
class Model_Record extends Model_base
{

	/**
	 * The record object which handles operations
	 * @var \DRY\Record\base
	 */
	protected $record;

	/**
	 * Serializer module used for attributes packing/unpacking before/after write/read
	 * @var \DRY\Module\Serializer
	 */
	protected $attr_serializer;


	/**
	 * Field names in the database
	 * @var array
	 */
	protected $fields;

	/**
	 * Document model constructor
	 * @param \DRY\Module\Document $document_module
	 * @param \SimpleXMLElement $CFG
	 * @param string $id
	 */
	public function __construct(\DRY\Module\Document $document_module, $CFG, $id = null)
	{
		// attributes
		if(isset($CFG-> attr)) {
			$this-> attr_serializer = $document_module['serializer'];
			$this-> fields['serialized'] = (string)($CFG-> attr-> serialized?:"metadata"); // defaults to "metadata" when field name isn't specified

			if($CFG-> attr-> directAccess)
				$this-> fields['directAccess'] = explode(',', $CFG-> attr-> directAccess);
		}

		$fields = array(
			'content'=> true, $this-> fields['serialized']=> true
		);
		if(isset($this-> fields['directAccess']))
			foreach($this-> fields['directAccess'] as $field)
				$fields[$field] = true;

		// Setup the record object
		$rec_module = $document_module[(string)$CFG-> storage-> depKey];

		$table_name = (string)($CFG-> storage-> table?:'documents');
		$id_field = (string)$CFG-> storage['idField']?:'id';
		$rec_module-> define_Record('_docmodel_'.$table_name, array(
			'type'=> "Single",
			'table'=> $table_name,
			'id_field'=> $id_field,
			'fields'=> $fields
		));
		$this-> record = $rec_module-> factory_Record('_docmodel_'.$table_name, $id);
	}

	public function __destruct()
	{
		if($this-> attr_changed)
			$this-> record-> {$this-> fields['serialized']} = $this-> attr_serializer-> serialize($this-> attributes);
	}

	/**
	 * Reads the document by accessing the "content" field in the record
	 */
	public function read()
	{
		return $this-> record-> content;
	}

	/**
	 * Creates or updates the document by setting the "content" field in the record
	 * @param string $content
	 */
	public function write($content = '')
	{
		$this-> record-> content = $content;
	}

	/**
	 * Destroys the record (cannot reverse on the document object !)
	 */
	public function destroy()
	{
		$this-> record-> destroy();
	}

	/**
	 * Attributes associated with the document
	 * @var array
	 */
	protected $attributes = array();

	/**
	 * Have attributes been loaded?
	 * @var bool
	 */
	protected $attr_loaded = false;

	/**
	 * Has any attribute been changed
	 * @var bool
	 */
	protected $attr_changed = false;

	/**
	 * Unpacks arbitrary attributes from the "serialized" field in the record ( usually named "metadata" )
	 */
	protected function unpack_Attributes()
	{
		$this-> attributes = $this-> attr_serializer-> unserialize($this-> record-> {$this-> fields['serialized']});
		$this-> attr_loaded = true;
	}

	public function __get($attr)
	{
		if(in_array($attr, $this-> fields['directAccess']))
			return $this-> record-> $attr;
		else {
			if(!$this-> attr_loaded)
				$this-> unpack_Attributes();
			if(isset($this-> attributes[$attr]))
				return $this-> attributes[$attr];
		}
	}

	/**
	 * Sets an attribute.
	 * Note: Attributes stored in direct access fields MUST be of scalar types (not array, object), as it's stored as-is by the record object.
	 * The others are packed using the serializer so they can be anything.
	 *
	 * @param string $attr
	 * @param mixed $value
	 */
	public function __set($attr, $value)
	{
		if(in_array($attr, $this-> fields['directAccess']))
			$this-> record-> $attr = $value;
		else {
			if(!$this-> attr_loaded)
				$this-> unpack_Attributes();
			$this-> attributes[$attr] = $value;
			$this-> attr_changed = true;
		}
	}

	/**
	 * Checks if an attribute exists.
	 * Checking for one of the "directAccess" attributes will always return true, for others it will unpack serialized attributes first.

	 * @param string $attr
	 */
	public function __isset($attr)
	{
		if(in_array($attr, $this-> fields['directAccess']))
			return true;
		else {
			if(!$this-> attr_loaded)
				$this-> unpack_Attributes();
			return isset($this-> attributes[$attr]);
		}
	}

	public function __unset($attr)
	{
		if(in_array($attr, $this-> fields['directAccess']))
			$this-> record-> $attr = null; // there is no unsetting of record field, however unset will clear this attribute instead
		else {
			if(!$this-> attr_loaded)
				$this-> unpack_Attributes();
			unset($this-> attributes[$attr]);
			$this-> attr_changed = true;
		}
	}

	/**
	 * Searches for an attribute value in the "documents" table, returning the first document that matches.
	 * Search can be extremely fast for directAccess fields, however very slow for attributes which are inside the serialized field
	 *   (because all records must be read, attributes unpacked one by one and the value searched. NOT recommended, use directAccess fields when necessary to search)
	 *
	 * @param \DRY\Module\Document $document_module
	 * @param \SimpleXMLElement $CFG
	 * @param string $attr
	 * @param mixed $value
	 * @param string $extension
	 */
	public static function search_Attribute(\DRY\Module\Document $document_module, $CFG, $attr, $value)
	{
		if(!isset($CFG-> attr))
			return null; // error, cannot search when attributes feature is disabled

		if($CFG-> attr-> directAccess)
			$fields['directAccess'] = explode(',', $CFG-> attr-> directAccess);

		// Record module (used here to discover the "connection" and "queryfactory" modules
		$rec_module = $document_module[(string)$CFG-> storage-> depKey];
		$table_name = (string)($CFG-> storage-> table?:'documents');
		$id_field = (string)$CFG-> storage['idField']?:'id';

		$q = $rec_module['queryfactory']-> select();

		// which fields are read from the table?
		$db_fields = array(
			$id_field
		);
		if(isset($fields['directAccess']) && in_array($attr, $fields['directAccess'])) {
			$q
				-> where("`$attr` = ?", $value)
				-> limit(1);
			$search_all = false;
		} else {
			$db_fields['metadata'] = (string)($CFG-> attr-> serialized?:"metadata"); // defaults to "metadata" when field name isn't specified
			$search_all = true;
		}

		$q-> table($table_name, $db_fields);

		if($search_all) {
			$r = $rec_module['connection']-> query($q);
			foreach($r as $row) {
				// unpack the serialized field and look for the attribute
				$attrs = $document_module['serializer']-> unserialize($row['metadata']);
				if(isset($attrs[$attr]) && $attrs[$attr]==$value) {
					// found it !
					$r-> close();
					return new self($document_module, $CFG, $row[$id_field]);
				}
			}
		} else {
			$row = $rec_module['connection']-> query($q)-> current();
			if($row)
				return new self($document_module, $CFG, $row[$id_field]);
		}
	}

	/**
	 * Runs a custom search on the "documents" table, using a callback that receives the QueryFactory object. The first result will become a Document object
	 *
	 * @param \DRY\Module\Document $document_module
	 * @param \SimpleXMLElement $CFG
	 * @param \DRY\Queryfactory\Dialect_base $qf_Callback
	 */
	public static function search_Custom(\DRY\Module\Document $document_module, $CFG, \DRY\Queryfactory\Dialect_base $qf_Callback)
	{

	}

}
